package model;

import java.util.ArrayList;
import java.util.List;

import javax.vecmath.Point3d;

import model.primitive.GenericMesh;
import model.primitive.IntersectionData;
import model.primitive.Primitive;
import model.primitive.Sphere;
import model.primitive.TriangleOld;
import model.ray.Ray;

public class AABB implements AABBConstants{
    
    private Point3d dmin;
    private Point3d dmax;
    private Primitive primitive;
	
    public Primitive getPrimitive() {
		return primitive;
	}

	public AABB( Primitive primitive ) {
        this.primitive = primitive;
        if( this.primitive instanceof GenericMesh ){
            List<TriangleOld> triangles=((GenericMesh)this.primitive).getTriangles();
            if( triangles.size()<2 )
                throw new IllegalArgumentException("invalid generic mesh triangles size");
            dmin=triangles.get(0).getVertex()[0];
            dmax=triangles.get(0).getVertex()[0];
            
            for(TriangleOld t: triangles){
                for(Point3d p: t.getVertex()){
                    build_DMin(p);
                    build_DMax(p);
                }
            }
        }
    }

	public AABB( TriangleOld triangle, Point3d dmin, Point3d dmax ){
		this.dmin = dmin;
		this.dmax = dmax;
		this.primitive = triangle;
	}
	
	public AABB(Point3d dmin, Point3d dmax) {
		this.dmin = dmin;
		this.dmax = dmax;
		this.primitive = null;
	}

	public static List<AABB> getGenericMeshAABB( GenericMesh primitive ) {
		List<AABB> list = new ArrayList<AABB>();
		for( TriangleOld triangle : primitive.getTriangles() ) {
			list.add( new AABB(triangle, triangle.getDmin(), triangle.getDmax()) );
		}
		return list;
		
	}
	
    public Point3d getDmin() {
    	if( dmin == null )
    		dmin=build_DMin();
		return dmin;
	}

	public Point3d getDmax() {
    	if( dmax == null )
    		dmax=build_DMax();
		return dmax;
	}

    public Point3d getCenter() {
		Point3d dMin = this.dmin;
		Point3d dMax = this.dmax;
		//Se calcula el centro del volumen envolvente para facilitar el recorrido del arbol
		return new Point3d( (dMax.x-dMin.x)/2, (dMax.y-dMin.y)/2, (dMax.z-dMin.z)/2  );
    }

    

    public void getIntersectsWithRay(Ray ray, List<IntersectionData> hits) {

        if(	this.primitive instanceof GenericMesh ||
        	this.primitive instanceof TriangleOld	) {
        	if( this.intersectsWithRay(ray) ) {
        		List<Point3d> aux = this.primitive.getIntersections(ray);
        		if( aux != null ) {
					for( Point3d intersectionHitPoint : aux )	{
						Double distance = ray.getDistance(intersectionHitPoint);
						IntersectionData intersectionData = new IntersectionData(ray, this.primitive, intersectionHitPoint, distance);
					 	hits.add(intersectionData);
					}
        		}
        	}
        } else {
        	List<Point3d> aux = primitive.getIntersections(ray);
    		if( aux != null ) {
				for( Point3d intersectionHitPoint : aux )	{
					Double distance = ray.getDistance(intersectionHitPoint);
					IntersectionData intersectionData = new IntersectionData(ray, primitive, intersectionHitPoint, distance);
				 	hits.add(intersectionData);
				}
    		}
        }
    }
    
    
	public boolean intersectsWithRay_Alejo(Ray ray) {
        double t_x0, t_y0, t_z0, t_x1, t_y1, t_z1, aux;
        //BSW es el punto minimo del octante, y FNE el maximo 
        t_x0 = (this.dmin.x - ray.origin.x)/ray.direction.x;
        t_x1 = (this.dmax.x - ray.origin.x)/ray.direction.x;
        if(t_x0 > t_x1)	{		//SWApEO para tener consistencia al comparar!!
        	
            aux = t_x0;
            
            t_x0 = t_x1;
            t_x1 = aux;
        }
        t_y0 = (this.dmin.y - ray.origin.y)/ray.direction.y;
        t_y1 = (this.dmax.y - ray.origin.y)/ray.direction.y;
        if(t_y0 > t_y1)	{
            aux = t_y0;
            t_y0 = t_y1;
            t_y1 = aux;
            
        }
        t_z0 = (this.dmin.z - ray.origin.z)/ray.direction.z;
        t_z1 = (this.dmax.z - ray.origin.z)/ray.direction.z;
        if(t_z0 > t_z1)	{
        	
                aux = t_z0;
                t_z0 = t_z1;
                t_z1 = aux;
        }
        if( t_x1 < t_y0 || t_y1 < t_z0 ||
        		t_x1 < t_z0 || t_y1 < t_x0 ||
       		 		t_z1 < t_x0 || t_z1 < t_y0 
       		 	 )
        	return false;
        return true;
	}
	
    private boolean intersectsWithRay(Ray ray) {
    	
    	switch (ray.getClasification()) {
		
		case MMM:
			
			if ((ray.getOrigin().x < dmin.x) || (ray.getOrigin().y < dmin.y) || (ray.getOrigin().z < dmin.z)
				|| (ray.getSlope(Slope.JI) * dmin.x - dmax.y + ray.getPrecomputation(Faces.XY) > 0)
				|| (ray.getSlope(Slope.IJ) * dmin.y - dmax.x + ray.getPrecomputation(Faces.YX) > 0)
				|| (ray.getSlope(Slope.JK) * dmin.z - dmax.y + ray.getPrecomputation(Faces.ZY) > 0)
				|| (ray.getSlope(Slope.KJ) * dmin.y - dmax.z + ray.getPrecomputation(Faces.YZ) > 0)
				|| (ray.getSlope(Slope.KI) * dmin.x - dmax.z + ray.getPrecomputation(Faces.XZ) > 0)
				|| (ray.getSlope(Slope.IK) * dmin.z - dmax.x + ray.getPrecomputation(Faces.ZX) > 0)
				)
				return false;
			
			return true;

		case MMP:
			
			if ((ray.getOrigin().x < dmin.x) || (ray.getOrigin().y < dmin.y) || (ray.getOrigin().z > dmax.z)
				|| (ray.getSlope(Slope.JI) * dmin.x - dmax.y + ray.getPrecomputation(Faces.XY) > 0)
				|| (ray.getSlope(Slope.IJ) * dmin.y - dmax.x + ray.getPrecomputation(Faces.YX) > 0)
				|| (ray.getSlope(Slope.JK) * dmax.z - dmax.y + ray.getPrecomputation(Faces.ZY) > 0)
				|| (ray.getSlope(Slope.KJ) * dmin.y - dmin.z + ray.getPrecomputation(Faces.YZ) < 0)
				|| (ray.getSlope(Slope.KI) * dmin.x - dmin.z + ray.getPrecomputation(Faces.XZ) < 0)
				|| (ray.getSlope(Slope.IK) * dmax.z - dmax.x + ray.getPrecomputation(Faces.ZX) > 0)
				)
				return false;
			
			return true;

		case MPM:
			
			if ((ray.getOrigin().x < dmin.x) || (ray.getOrigin().y > dmax.y) || (ray.getOrigin().z < dmin.z)
				|| (ray.getSlope(Slope.JI) * dmin.x - dmin.y + ray.getPrecomputation(Faces.XY) < 0) 
				|| (ray.getSlope(Slope.IJ) * dmax.y - dmax.x + ray.getPrecomputation(Faces.YX) > 0)
				|| (ray.getSlope(Slope.JK) * dmin.z - dmin.y + ray.getPrecomputation(Faces.ZY) < 0) 
				|| (ray.getSlope(Slope.KJ) * dmax.y - dmax.z + ray.getPrecomputation(Faces.YZ) > 0)
				|| (ray.getSlope(Slope.KI) * dmin.x - dmax.z + ray.getPrecomputation(Faces.XZ) > 0)
				|| (ray.getSlope(Slope.IK) * dmin.z - dmax.x + ray.getPrecomputation(Faces.ZX) > 0)
				)
				return false;
			
			return true;

		case MPP:
		
			if ((ray.getOrigin().x < dmin.x) || (ray.getOrigin().y > dmax.y) || (ray.getOrigin().z > dmax.z)
				|| (ray.getSlope(Slope.JI) * dmin.x - dmin.y + ray.getPrecomputation(Faces.XY) < 0) 
				|| (ray.getSlope(Slope.IJ) * dmax.y - dmax.x + ray.getPrecomputation(Faces.YX) > 0)
				|| (ray.getSlope(Slope.JK) * dmax.z - dmin.y + ray.getPrecomputation(Faces.ZY) < 0)
				|| (ray.getSlope(Slope.KJ) * dmax.y - dmin.z + ray.getPrecomputation(Faces.YZ) < 0)
				|| (ray.getSlope(Slope.KI) * dmin.x - dmin.z + ray.getPrecomputation(Faces.XZ) < 0)
				|| (ray.getSlope(Slope.IK) * dmax.z - dmax.x + ray.getPrecomputation(Faces.ZX) > 0)
				)
				return false;
			
			return true;

		case PMM:

			if ((ray.getOrigin().x > dmax.x) || (ray.getOrigin().y < dmin.y) || (ray.getOrigin().z < dmin.z)
				|| (ray.getSlope(Slope.JI) * dmax.x - dmax.y + ray.getPrecomputation(Faces.XY) > 0)
				|| (ray.getSlope(Slope.IJ) * dmin.y - dmin.x + ray.getPrecomputation(Faces.YX) < 0)
				|| (ray.getSlope(Slope.JK) * dmin.z - dmax.y + ray.getPrecomputation(Faces.ZY) > 0)
				|| (ray.getSlope(Slope.KJ) * dmin.y - dmax.z + ray.getPrecomputation(Faces.YZ) > 0)
				|| (ray.getSlope(Slope.KI) * dmax.x - dmax.z + ray.getPrecomputation(Faces.XZ) > 0)
				|| (ray.getSlope(Slope.IK) * dmin.z - dmin.x + ray.getPrecomputation(Faces.ZX) < 0)
				)
				return false;

			return true;

		case PMP:

			if ((ray.getOrigin().x > dmax.x) || (ray.getOrigin().y < dmin.y) || (ray.getOrigin().z > dmax.z)
				|| (ray.getSlope(Slope.JI) * dmax.x - dmax.y + ray.getPrecomputation(Faces.XY) > 0)
				|| (ray.getSlope(Slope.IJ) * dmin.y - dmin.x + ray.getPrecomputation(Faces.YX) < 0)
				|| (ray.getSlope(Slope.JK) * dmax.z - dmax.y + ray.getPrecomputation(Faces.ZY) > 0)
				|| (ray.getSlope(Slope.KJ) * dmin.y - dmin.z + ray.getPrecomputation(Faces.YZ) < 0)
				|| (ray.getSlope(Slope.KI) * dmax.x - dmin.z + ray.getPrecomputation(Faces.XZ) < 0)
				|| (ray.getSlope(Slope.IK) * dmax.z - dmin.x + ray.getPrecomputation(Faces.ZX) < 0)
				)
				return false;

			return true;

		case PPM:

			if ((ray.getOrigin().x > dmax.x) || (ray.getOrigin().y > dmax.y) || (ray.getOrigin().z < dmin.z)
				|| (ray.getSlope(Slope.JI) * dmax.x - dmin.y + ray.getPrecomputation(Faces.XY) < 0)
				|| (ray.getSlope(Slope.IJ) * dmax.y - dmin.x + ray.getPrecomputation(Faces.YX) < 0)
				|| (ray.getSlope(Slope.JK) * dmin.z - dmin.y + ray.getPrecomputation(Faces.ZY) < 0) 
				|| (ray.getSlope(Slope.KJ) * dmax.y - dmax.z + ray.getPrecomputation(Faces.YZ) > 0)
				|| (ray.getSlope(Slope.KI) * dmax.x - dmax.z + ray.getPrecomputation(Faces.XZ) > 0)
				|| (ray.getSlope(Slope.IK) * dmin.z - dmin.x + ray.getPrecomputation(Faces.ZX) < 0)
				)
				return false;
			
			return true;

		case PPP:

			if ((ray.getOrigin().x > dmax.x) || (ray.getOrigin().y > dmax.y) || (ray.getOrigin().z > dmax.z)
				|| (ray.getSlope(Slope.JI) * dmax.x - dmin.y + ray.getPrecomputation(Faces.XY) < 0)
				|| (ray.getSlope(Slope.IJ) * dmax.y - dmin.x + ray.getPrecomputation(Faces.YX) < 0)
				|| (ray.getSlope(Slope.JK) * dmax.z - dmin.y + ray.getPrecomputation(Faces.ZY) < 0)
				|| (ray.getSlope(Slope.KJ) * dmax.y - dmin.z + ray.getPrecomputation(Faces.YZ) < 0)
				|| (ray.getSlope(Slope.KI) * dmax.x - dmin.z + ray.getPrecomputation(Faces.XZ) < 0)
				|| (ray.getSlope(Slope.IK) * dmax.z - dmin.x + ray.getPrecomputation(Faces.ZX) < 0)
				)
				return false;
			
			return true;

		case OMM:

			if((ray.getOrigin().x < dmin.x) || (ray.getOrigin().x > dmax.x)
				|| (ray.getOrigin().y < dmin.y) || (ray.getOrigin().z < dmin.z)
				|| (ray.getSlope(Slope.JK) * dmin.z - dmax.y + ray.getPrecomputation(Faces.ZY) > 0)
				|| (ray.getSlope(Slope.KJ) * dmin.y - dmax.z + ray.getPrecomputation(Faces.YZ) > 0)
				)
				return false;

			return true;

		case OMP:

			if((ray.getOrigin().x < dmin.x) || (ray.getOrigin().x > dmax.x)
				|| (ray.getOrigin().y < dmin.y) || (ray.getOrigin().z > dmax.z)
				|| (ray.getSlope(Slope.JK) * dmax.z - dmax.y + ray.getPrecomputation(Faces.ZY) > 0)
				|| (ray.getSlope(Slope.KJ) * dmin.y - dmin.z + ray.getPrecomputation(Faces.YZ) < 0)
				)
				return false;

			return true;

		case OPM:

			if((ray.getOrigin().x < dmin.x) || (ray.getOrigin().x > dmax.x)
				|| (ray.getOrigin().y > dmax.y) || (ray.getOrigin().z < dmin.z)
				|| (ray.getSlope(Slope.JK) * dmin.z - dmin.y + ray.getPrecomputation(Faces.ZY) < 0) 
				|| (ray.getSlope(Slope.KJ) * dmax.y - dmax.z + ray.getPrecomputation(Faces.YZ) > 0)
				)
				return false;

			return true;

		case OPP:

			if((ray.getOrigin().x < dmin.x) || (ray.getOrigin().x > dmax.x)
				|| (ray.getOrigin().y > dmax.y) || (ray.getOrigin().z > dmax.z)
				|| (ray.getSlope(Slope.JK) * dmax.z - dmin.y + ray.getPrecomputation(Faces.ZY) < 0)
				|| (ray.getSlope(Slope.KJ) * dmax.y - dmin.z + ray.getPrecomputation(Faces.YZ) < 0)
				)
				return false;

			return true;

		case MOM:

			if((ray.getOrigin().y < dmin.y) || (ray.getOrigin().y > dmax.y)
				|| (ray.getOrigin().x < dmin.x) || (ray.getOrigin().z < dmin.z) 
				|| (ray.getSlope(Slope.KI) * dmin.x - dmax.z + ray.getPrecomputation(Faces.XZ) > 0)
				|| (ray.getSlope(Slope.IK) * dmin.z - dmax.x + ray.getPrecomputation(Faces.ZX) > 0)
				)
				return false;

			return true;

		case MOP:

			if((ray.getOrigin().y < dmin.y) || (ray.getOrigin().y > dmax.y)
				|| (ray.getOrigin().x < dmin.x) || (ray.getOrigin().z > dmax.z) 
				|| (ray.getSlope(Slope.KI) * dmin.x - dmin.z + ray.getPrecomputation(Faces.XZ) < 0)
				|| (ray.getSlope(Slope.IK) * dmax.z - dmax.x + ray.getPrecomputation(Faces.ZX) > 0)
				)
				return false;

			return true;

		case POM:

			if((ray.getOrigin().y < dmin.y) || (ray.getOrigin().y > dmax.y)
				|| (ray.getOrigin().x > dmax.x) || (ray.getOrigin().z < dmin.z)
				|| (ray.getSlope(Slope.KI) * dmax.x - dmax.z + ray.getPrecomputation(Faces.XZ) > 0)
				|| (ray.getSlope(Slope.IK) * dmin.z - dmin.x + ray.getPrecomputation(Faces.ZX) < 0)
				)
				return false;

			return true;

		case POP:

			if((ray.getOrigin().y < dmin.y) || (ray.getOrigin().y > dmax.y)
				|| (ray.getOrigin().x > dmax.x) || (ray.getOrigin().z > dmax.z)
				|| (ray.getSlope(Slope.KI) * dmax.x - dmin.z + ray.getPrecomputation(Faces.XZ) < 0)
				|| (ray.getSlope(Slope.IK) * dmax.z - dmin.x + ray.getPrecomputation(Faces.ZX) < 0)
				)
				return false;

			return true;

		case MMO:

			if((ray.getOrigin().z < dmin.z) || (ray.getOrigin().z > dmax.z)
				|| (ray.getOrigin().x < dmin.x) || (ray.getOrigin().y < dmin.y) 
				|| (ray.getSlope(Slope.JI) * dmin.x - dmax.y + ray.getPrecomputation(Faces.XY) > 0)
				|| (ray.getSlope(Slope.IJ) * dmin.y - dmax.x + ray.getPrecomputation(Faces.YX) > 0)
				)
				return false;

			return true;

		case MPO:

			if((ray.getOrigin().z < dmin.z) || (ray.getOrigin().z > dmax.z)
				|| (ray.getOrigin().x < dmin.x) || (ray.getOrigin().y > dmax.y) 
				|| (ray.getSlope(Slope.JI) * dmin.x - dmin.y + ray.getPrecomputation(Faces.XY) < 0) 
				|| (ray.getSlope(Slope.IJ) * dmax.y - dmax.x + ray.getPrecomputation(Faces.YX) > 0)
				)
				return false;

			return true;

		case PMO:

			if((ray.getOrigin().z < dmin.z) || (ray.getOrigin().z > dmax.z)
				|| (ray.getOrigin().x > dmax.x) || (ray.getOrigin().y < dmin.y) 
				|| (ray.getSlope(Slope.JI) * dmax.x - dmax.y + ray.getPrecomputation(Faces.XY) > 0)
				|| (ray.getSlope(Slope.IJ) * dmin.y - dmin.x + ray.getPrecomputation(Faces.YX) < 0)  
				)
				return false;

			return true;

		case PPO:

			if((ray.getOrigin().z < dmin.z) || (ray.getOrigin().z > dmax.z)
				|| (ray.getOrigin().x > dmax.x) || (ray.getOrigin().y > dmax.y)
				|| (ray.getSlope(Slope.JI) * dmax.x - dmin.y + ray.getPrecomputation(Faces.XY) < 0)
				|| (ray.getSlope(Slope.IJ) * dmax.y - dmin.x + ray.getPrecomputation(Faces.YX) < 0)
				)
				return false;

			return true;

		case MOO:

			if((ray.getOrigin().x < dmin.x)
				|| (ray.getOrigin().y < dmin.y) || (ray.getOrigin().y > dmax.y)
				|| (ray.getOrigin().z < dmin.z) || (ray.getOrigin().z > dmax.z)
				)
				return false;

			return true;

		case POO:

			if((ray.getOrigin().x > dmax.x)
				|| (ray.getOrigin().y < dmin.y) || (ray.getOrigin().y > dmax.y)
				|| (ray.getOrigin().z < dmin.z) || (ray.getOrigin().z > dmax.z)
				)
				return false;

			return true;

		case OMO:

			if((ray.getOrigin().y < dmin.y)
				|| (ray.getOrigin().x < dmin.x) || (ray.getOrigin().x > dmax.x)
				|| (ray.getOrigin().z < dmin.z) || (ray.getOrigin().z > dmax.z)
				)
				return false;

		case OPO:

			if((ray.getOrigin().y > dmax.y)
				|| (ray.getOrigin().x < dmin.x) || (ray.getOrigin().x > dmax.x)
				|| (ray.getOrigin().z < dmin.z) || (ray.getOrigin().z > dmax.z)
				)
				return false;

		case OOM:

			if((ray.getOrigin().z < dmin.z)
				|| (ray.getOrigin().x < dmin.x) || (ray.getOrigin().x > dmax.x)
				|| (ray.getOrigin().y < dmin.y) || (ray.getOrigin().y > dmax.y)
				)
				return false;

		case OOP:

			if((ray.getOrigin().z > dmax.z)
				|| (ray.getOrigin().x < dmin.x) || (ray.getOrigin().x > dmax.x)
				|| (ray.getOrigin().y < dmin.y) || (ray.getOrigin().y > dmax.y)
				)
				return false;

			return true;
		}
    		
		return false;
	}

    
    // COMO MODIFICO LOS POINT3d ????????????????????????????
   	private void build_DMin( Point3d p){
        if(this.dmin.x>p.x)
        	this.dmin=new Point3d(p.x,dmin.y,dmin.z);
        if(this.dmin.y>p.y)
            this.dmin=new Point3d(dmin.x,p.y,dmin.z);
        if(this.dmin.z>p.z)
            this.dmin=new Point3d(dmin.x,dmin.y,p.z);
    }

    private void build_DMax(Point3d p){    	   	
        if(this.dmax.x<p.x)
        	this.dmax=new Point3d(p.x,dmax.y,dmax.z);
        if(this.dmax.y<p.y)
            this.dmax=new Point3d(dmax.x,p.y,dmax.z);
        if(this.dmax.z<p.z)
            this.dmax=new Point3d(dmax.x,dmax.y,p.z);    
    }

    
    private Point3d build_DMin() {
        if( this.primitive instanceof Sphere ){
        	Sphere sphere = ((Sphere)this.primitive);
        	Double radius = sphere.getRadius();
        	return new Point3d( sphere.getCenter().x - radius, sphere.getCenter().y - radius, sphere.getCenter().z - radius);
        }
		return new Point3d(0,0,0);
	}
    
    private Point3d build_DMax() {
    	if( this.primitive instanceof Sphere ){
        	Sphere sphere = ((Sphere)this.primitive);
        	Double radius = sphere.getRadius();
        	return new Point3d( sphere.getCenter().x + radius, sphere.getCenter().y + radius, sphere.getCenter().z + radius);
        }
		return null;
	}
    

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		AABB other = (AABB) obj;
		if (dmin == null) {
			if (other.dmin != null)
				return false;
		} else if (dmax == null) {
			if (other.dmax != null)
				return false;
		} else if (!dmin.equals(other.dmin)) {
			return false;
		} else if (!dmax.equals(other.dmax)) {
			return false;
		}
		return true;
	}


	


}